﻿using System;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Windows.Forms;
using AForge.Video;
using AForge.Video.DirectShow;
using WindowsFormsGUI.Entidades;

namespace WindowsFormsGUI
{
    public partial class Home : Form
    {
        #region Atributos
        
        private SerialPort _puertoSerie;
        private bool _error;
        private bool _existeDispositivo;
        private bool _dispositivoEnEjecucion;
        private FilterInfoCollection _dispositivoDeVideo;
        private VideoCaptureDevice _fuenteDeVideo;
        private ProcesadorDeImagen _procesadorDeImagen;
        
        #endregion

        #region Constructores

        public Home()
        {
            InitializeComponent();

            _puertoSerie = new SerialPort();
            ControladorPuertoSerie.EstablecerPuertoSerie(_puertoSerie);
            
            ObtenerPuertos();
            BuscarDispositivos();
            EstablecerContadores();

            _procesadorDeImagen = new ProcesadorDeImagen();
            // Cuando el dato es recibido por el puerto, se llama a este metodo
            _puertoSerie.DataReceived += new SerialDataReceivedEventHandler(RecibirDatos);           
            //Desabilitamos la detección de llamadas a los miembros del proceso principal
            CheckForIllegalCrossThreadCalls = false;
        }

        #endregion

        #region Eventos de usuario

        private void BtnSalirClick(object sender, EventArgs e)
        {
            try
            {
                TerminarFuenteDeVideo();
                this.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
            }
        }

        private void BtnAbrirPuertoClick(object sender, EventArgs e)
        {
            if (_puertoSerie.IsOpen)
            {
                _puertoSerie.Close();

                lblPuerto.Text = _puertoSerie.PortName + " CERRADO";
                lblPuerto.BackColor = Color.Red;
            }
            else
            {
                _puertoSerie.PortName = cmbPuerto.Text;

                try
                {
                    _puertoSerie.Open();

                    tbxConsola.Clear();

                    lblPuerto.Text = _puertoSerie.PortName + " ABIERTO";
                    lblPuerto.BackColor = Color.Green;

                    tbxConsola.AppendText("Puerto abierto a las " + DateTime.Now + "\n");
                    tbxConsola.AppendText("BaudRate: " + _puertoSerie.BaudRate + "\n");
                    tbxConsola.AppendText("DataBits: " + _puertoSerie.DataBits + "\n");
                    tbxConsola.AppendText("StopBits: " + _puertoSerie.StopBits + "\n");
                    tbxConsola.AppendText("Parity: " + _puertoSerie.Parity + "\n");
                }
                catch (UnauthorizedAccessException ex)
                {
                    _error = true;
                    tbxConsola.AppendText("\n" + ex.Message);
                }
                catch (IOException ex)
                {
                    _error = true;
                    tbxConsola.AppendText("\n" + ex.Message);
                }
                catch (ArgumentException ex)
                {
                    _error = true;
                    tbxConsola.AppendText("\n" + ex.Message);
                }

                if (_error)
                {
                    MessageBox.Show(this, "No se pudo abrir el puerto COM. Lo más probable es que ya está en uso, ha sido eliminado o no está disponible.", "Puerto COM no disponible", MessageBoxButtons.OK, MessageBoxIcon.Stop);
                }
            }
        }

        private void BtnCerrarPuertoClick(object sender, EventArgs e)
        {
            try
            {
                if (_puertoSerie.IsOpen)
                {
                    _puertoSerie.Close();

                    lblPuerto.Text = _puertoSerie.PortName + " CERRADO";
                    lblPuerto.BackColor = Color.Red;

                    tbxConsola.Clear();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(this, "No se pudo cerrar el puerto COM. Lo más probable es que ya está en uso, ha sido eliminado o no está disponible.", "Puerto COM no disponible", MessageBoxButtons.OK, MessageBoxIcon.Stop);
            }
        }

        private void BtnIniciarClick(object sender, EventArgs e)
        {
            try
            {
                if (_dispositivoEnEjecucion)
                {
                    DetenerDispositivo();
                }
                else
                {
                    IniciarDispositivo();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
            }
        }

        private void BtnProcesarImagenClick(object sender, EventArgs e)
        {
            try
            {
                if (VerificarPerifericos())
                {
                    EspacioImagen.Image = EspacioCamara.Image;
                    var moneda = ProcesarImagen();                                        
                    DireccionarMoneda(moneda);
                }                
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
            }
        }

        private void BtnCargarImagenBaseClick(object sender, EventArgs e)
        {
            try
            {
                _procesadorDeImagen.Configurar();
                
                lblEstadoParametros.Text = "Parámetros cargados";
                lblEstadoParametros.BackColor = Color.Green;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
            }
        }

        #endregion

        #region Metodos de soporte

        private void RecibirDatos(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                var datos = ControladorPuertoSerie.LeerDatosDesdePuerto();
                var posicion = (PosicionEnum)datos;

                switch (posicion)
                {
                    case PosicionEnum.Inicio:
                        ConsolaLog("Posicion [0] : Inicial. \n");
                        break;
                    case PosicionEnum.CincoCentavos:
                        ConsolaLog("Posicion [1] : 5 ctvos. \n");
                        break;
                    case PosicionEnum.DiezCentavos:
                        ConsolaLog("Posicion [2] : 10 ctvos. \n");
                        break;
                    case PosicionEnum.VeinticincoCentavos:
                        ConsolaLog("Posicion [3] : 25 ctvos. \n");
                        break;
                    case PosicionEnum.CincuentaCentavos:
                        ConsolaLog("Posicion [4] : 50 ctvos. \n");
                        break;
                    case PosicionEnum.UnPeso:
                        ConsolaLog("Posicion [5] : 1 peso. \n");
                        break;
                    case PosicionEnum.Falsa:
                        ConsolaLog("Posicion [6] : moneda falsa. \n");
                        break;
                    case PosicionEnum.Error:
                        ConsolaLog("Error : Ha ocurrido un error inesperado. \n");
                        break;
                    default:
                        ConsolaLog("Error : Ha ocurrido un error inesperado. \n");
                        break;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);      
            }
        }

        private void DireccionarMoneda(Moneda moneda)
        {
            if (moneda == null)
            {
                ControladorPuertoSerie.EnviarPosicion(PosicionEnum.Falsa);    
            }
            else
            {
                switch (moneda.Nombre)
                {
                    case DenominacionEnum.CincoCentavos:
                        ControladorPuertoSerie.EnviarPosicion(PosicionEnum.CincoCentavos);
                        break;
                    case DenominacionEnum.DiezCentavos:
                        ControladorPuertoSerie.EnviarPosicion(PosicionEnum.DiezCentavos);
                        break;
                    case DenominacionEnum.VeinticincoCentavos:
                        ControladorPuertoSerie.EnviarPosicion(PosicionEnum.VeinticincoCentavos);
                        break;
                    case DenominacionEnum.CincuentaCentavos:
                        ControladorPuertoSerie.EnviarPosicion(PosicionEnum.CincuentaCentavos);
                        break;
                    case DenominacionEnum.UnPeso:
                        ControladorPuertoSerie.EnviarPosicion(PosicionEnum.UnPeso);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                } 
            }
        }

        private Moneda ProcesarImagen()
        {
            var moneda = _procesadorDeImagen.Clasificar(EspacioImagen.Image);

            if (moneda == null)
            {
                Contador.MonedasFalsas++;
                MessageBox.Show("No se reconoce la moneda, puede que sea falsa.", "Informacion", MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1);
            }
            else
            {
                Contador.Agregar(moneda);
                MessageBox.Show("La moneda es: " + moneda.Nombre, "Informacion", MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1);
            }

            return moneda;
        }

        private void ObtenerPuertos()
        {
            var listaPuertosSerie = SerialPort.GetPortNames().ToList();
            cmbPuerto.DataSource = listaPuertosSerie;
        }

        private void BuscarDispositivos()
        {
            _dispositivoDeVideo = new FilterInfoCollection(FilterCategory.VideoInputDevice);

            if (_dispositivoDeVideo.Count > 0)
            {
                _existeDispositivo = true;
                CargarDispositivos(_dispositivoDeVideo);
            }
            else
            {
                _existeDispositivo = false;
            }
        }

        private void CargarDispositivos(FilterInfoCollection dispositivos)
        {
            foreach (FilterInfo dispositivo in dispositivos)
            {
                cmbDispositivos.Items.Add(dispositivo.Name);
                cmbDispositivos.Text = dispositivo.Name;
            }
        }

        private void IniciarDispositivo()
        {
            if (_existeDispositivo)
            {
                var idDispositivo = cmbDispositivos.SelectedIndex;

                _fuenteDeVideo = new VideoCaptureDevice(_dispositivoDeVideo[idDispositivo].MonikerString);
                _fuenteDeVideo.NewFrame += new NewFrameEventHandler(VideoNuevoFrame);
                _fuenteDeVideo.Start();

                Estado.Text = "Ejecutando dispositivo…";
                btnIniciar.Text = "Detener";

                _dispositivoEnEjecucion = true;

                HabilitarControles();
            }
            else
            {
                Estado.Text = "Error: No se encuenta el dispositivo";
            }
        }

        private void DetenerDispositivo()
        {
            if (_fuenteDeVideo.IsRunning)
            {
                TerminarFuenteDeVideo();

                Estado.Text = "Dispositivo detenido…";
                btnIniciar.Text = "Iniciar";

                _dispositivoEnEjecucion = false;

                HabilitarControles();
            }
        }

        private void TerminarFuenteDeVideo()
        {
            if (_fuenteDeVideo != null && _fuenteDeVideo.IsRunning)
            {
                _fuenteDeVideo.SignalToStop();
                _fuenteDeVideo = null;

                EspacioCamara.Image = null;
            }
        }

        private void VideoNuevoFrame(object sender, NewFrameEventArgs eventArgs)
        {
            var imagen = (Bitmap)eventArgs.Frame.Clone();
            EspacioCamara.Image = imagen;
        }

        private void HabilitarControles()
        {
            cmbDispositivos.Enabled = !_dispositivoEnEjecucion;
        }

        private bool VerificarPerifericos()
        {
            if (_puertoSerie == null || !_puertoSerie.IsOpen)
            {
                MessageBox.Show("El puerto se encuentra cerrado.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
                return false;
            }

            if (_fuenteDeVideo == null || !_fuenteDeVideo.IsRunning)
            {
                MessageBox.Show("La fuente de video se encuentra apagada.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
                return false;
            }

            return true;
        }

        private void EstablecerContadores()
        {
            lblMontoTotal.Text = Contador.MontoTotal().ToString(CultureInfo.InvariantCulture);

            lblCantProcesadas.Text = Contador.CantidadDeMonedasProcesadas().ToString(CultureInfo.InvariantCulture);
            lblCantVerdaderas.Text = Contador.CantidadDeMonedasVerdaderas().ToString(CultureInfo.InvariantCulture);
            lblCantFalsas.Text = Contador.MonedasFalsas.ToString(CultureInfo.InvariantCulture);

            lblCant5C.Text = Contador.Moneda5Centavos.ToString(CultureInfo.InvariantCulture);
            lblMonto5C.Text = Contador.Monto5Centavos().ToString(CultureInfo.InvariantCulture);

            lblCant10C.Text = Contador.Moneda10Centavos.ToString(CultureInfo.InvariantCulture);
            lblMonto10C.Text = Contador.Monto10Centavos().ToString(CultureInfo.InvariantCulture);

            lblCant25C.Text = Contador.Moneda25Centavos.ToString(CultureInfo.InvariantCulture);
            lblMonto25C.Text = Contador.Monto25Centavos().ToString(CultureInfo.InvariantCulture);

            lblCant50C.Text = Contador.Moneda50Centavos.ToString(CultureInfo.InvariantCulture);
            lblMonto50C.Text = Contador.Monto50Centavos().ToString(CultureInfo.InvariantCulture);

            lblCant1P.Text = Contador.Moneda1Peso.ToString(CultureInfo.InvariantCulture);
            lblMonto1P.Text = Contador.Moneda1Peso.ToString(CultureInfo.InvariantCulture);
        }

        private void ConsolaLog(string log)
        {
            tbxConsola.AppendText(log);
        }

        #endregion                               
    }
}
